/***************************************************************************************
* Copyright (c) 2014-2022 Zihao Yu, Nanjing University
*
* NEMU is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*          http://license.coscl.org.cn/MulanPSL2
*
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
*
* See the Mulan PSL v2 for more details.
***************************************************************************************/

#include <memory/host.h>
#include <memory/paddr.h>
#include <device/mmio.h>
#include <isa.h>

#if   defined(CONFIG_PMEM_MALLOC)
static uint8_t *pmem = NULL;
#else // CONFIG_PMEM_GARRAY
static uint8_t pmem[CONFIG_MSIZE] PG_ALIGN = {};
#endif

// SRAM
static uint8_t sram[0x2000]; // 0x0f00_0000~0x0f00_1fff
// PSRAM
static uint8_t psram[0x20000000]; // 0x8000_0000~0x9fff_ffff
// SDRAM
static uint8_t sdram[0x20000000]; // 0xa000_0000~0xbfff_ffff

// for sram
uint8_t* guest_to_host_sram(paddr_t paddr) { return sram + paddr - 0x0f000000; }
// for psram
uint8_t* guest_to_host_psram(paddr_t paddr) { return psram + paddr - 0x80000000; }
// for sdram
uint8_t* guest_to_host_sdram(paddr_t paddr) { return sdram + paddr - 0xa0000000; }
// form pmem
uint8_t* guest_to_host(paddr_t paddr) { return pmem + paddr - CONFIG_MBASE; }
paddr_t host_to_guest(uint8_t *haddr) { return haddr - pmem + CONFIG_MBASE; }

static word_t pmem_read(paddr_t addr, int len) {
  word_t ret = host_read(guest_to_host(addr), len);
  return ret;
}

static void pmem_write(paddr_t addr, int len, word_t data) {
  host_write(guest_to_host(addr), len, data);
}

static void out_of_bound(paddr_t addr) {
  panic("address = " FMT_PADDR " is out of bound of pmem [" FMT_PADDR ", " FMT_PADDR "] at pc = " FMT_WORD,
      addr, PMEM_LEFT, PMEM_RIGHT, cpu.pc);
}

void init_mem() {
#if   defined(CONFIG_PMEM_MALLOC)
  pmem = malloc(CONFIG_MSIZE);
  assert(pmem);
#endif
  IFDEF(CONFIG_MEM_RANDOM, memset(pmem, rand(), CONFIG_MSIZE));
  Log("physical memory area [" FMT_PADDR ", " FMT_PADDR "]", PMEM_LEFT, PMEM_RIGHT);
}

// NOTE: mtrace can not track reading mrom and sram
word_t paddr_read(paddr_t addr, int len) {
  word_t data = 0;

  if (addr >= 0x0f000000 && addr <= 0x0f001fff) {
    data = host_read(guest_to_host_sram(addr), len);
    #ifdef CONFIG_MTRACE
      printf("Reading %d byte(s) form 0x%08x, data:0x%08x\n", len, addr, data);
    #endif
    return data;
  }
  if (addr >= 0x80000000 && addr <= 0x9fffffff) {
    data = host_read(guest_to_host_psram(addr), len);
    #ifdef CONFIG_MTRACE
      printf("Reading %d byte(s) form 0x%08x, data:0x%08x\n", len, addr, data);
    #endif
    return data;
  }
  if (addr >= 0xa0000000 && addr <= 0xbfffffff) {
    data = host_read(guest_to_host_sdram(addr), len);
    #ifdef CONFIG_MTRACE
      printf("Reading %d byte(s) form 0x%08x, data:0x%08x\n", len, addr, data);
    #endif
    return data;
  }

  if (likely(in_pmem(addr))) {
    data = pmem_read(addr, len);
    #ifdef CONFIG_MTRACE
      printf("Reading %d byte(s) form 0x%08x, data:0x%08x\n", len, addr, data);
    #endif

    return data;
  }
  IFDEF(CONFIG_DEVICE, data = mmio_read(addr, len));
  #ifdef CONFIG_DEVICE
    #ifdef CONFIG_MTRACE
      printf("Reading %d byte(s) form 0x%08x, data:0x%08x\n", len, addr, data);
    #endif

    return data;
  #endif

  out_of_bound(addr);
  return 0;
}

void paddr_write(paddr_t addr, int len, word_t data) {
#ifdef CONFIG_MTRACE
  printf("Writing %d byte(s) into 0x%08x, data:0x%08x\n", len, addr, data);
#endif

  if (addr >= 0x20000000 && addr <= 0x20000fff) { Log("Can't write mrom[0x2000_0000~0x2000_0fff]"); assert(0); }
  if (addr >= 0x0f000000 && addr <= 0x0f001fff) { host_write(guest_to_host_sram(addr), len, data); return; }
  if (addr >= 0x80000000 && addr <= 0x9fffffff) { host_write(guest_to_host_psram(addr), len, data); return; }
  if (addr >= 0xa0000000 && addr <= 0xbfffffff) { host_write(guest_to_host_sdram(addr), len, data); return; }

  if (likely(in_pmem(addr))) { pmem_write(addr, len, data); return; }
  IFDEF(CONFIG_DEVICE, mmio_write(addr, len, data); return);
  out_of_bound(addr);
}
